
#include <irrlicht.h>
#include <iostream>

using namespace std;
using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;

#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif

IrrlichtDevice* device = 0;

class MirrorShaderCallBack : public video::IShaderConstantSetCallBack
{
public:

	virtual void OnSetConstants (video::IMaterialRendererServices* services, s32 userData)
	{
		video::IVideoDriver* driver = services->getVideoDriver();

        // VERTEX SHADER CONSTANTS

		// set inverted world matrix
		core::matrix4 invWorld = driver->getTransform(video::ETS_WORLD);
		invWorld.makeInverse();
        services->setVertexShaderConstant("mInvWorld", invWorld.pointer(), 16);

		// set clip matrix
		core::matrix4 worldViewProj;
		worldViewProj = driver->getTransform(video::ETS_PROJECTION);
		worldViewProj *= driver->getTransform(video::ETS_VIEW);
		worldViewProj *= driver->getTransform(video::ETS_WORLD);
        services->setVertexShaderConstant("mWorldViewProj", worldViewProj.pointer(), 16);

		// set camera position
		core::vector3df pos = device->getSceneManager()->getActiveCamera()->getAbsolutePosition();

        services->setVertexShaderConstant("mLightPos", reinterpret_cast<f32*>(&pos), 3);

		// set light color
		video::SColorf col(0.0f,1.0f,1.0f,0.0f);

        services->setVertexShaderConstant ("mLightColor", reinterpret_cast<f32*>(&col), 4);

		// set transposed world matrix
		core::matrix4 world = driver->getTransform(video::ETS_WORLD);
		world = world.getTransposed();
        services->setVertexShaderConstant ("mTransWorld", world.pointer(), 16);


        // PIXEL SHADER CONSTANTS

        f32 windowSize[2];
        windowSize[0] = (f32) driver->getScreenSize().Width;
        windowSize[1] = (f32) driver->getScreenSize().Height;
        services->setPixelShaderConstant ("windowSize", reinterpret_cast<f32*>(&windowSize), 2);

        int tex1=0;
        services->setPixelShaderConstant ("tex1", (float*)(&tex1), 1);
        int tex2=1;
        services->setPixelShaderConstant("tex2", (float*)(&tex2), 1);
	}
};


int main()
{
    // Only OpenGL for now :(
	device = createDevice (video::EDT_OPENGL, core::dimension2d<u32> (1024, 768));
	if (device == 0)
		return 1; // could not create selected driver.

	video::IVideoDriver* driver = device->getVideoDriver();
	scene::ISceneManager* smgr = device->getSceneManager();
	gui::IGUIEnvironment* gui = device->getGUIEnvironment();


	// MIRROR MATERIAL

	io::path vsFileName; // filename for the vertex shader
	io::path psFileName; // filename for the pixel shader

        psFileName = "./openGLmirror.frag";
        vsFileName = "./openGLmirror.vert";


	if (!driver->queryFeature(video::EVDF_PIXEL_SHADER_1_1) &&
		!driver->queryFeature(video::EVDF_ARB_FRAGMENT_PROGRAM_1))
	{
		device->getLogger()->log("WARNING: Pixel shaders disabled "\
			"because of missing driver/hardware support.");
		psFileName = "";
	}
	if (!driver->queryFeature(video::EVDF_VERTEX_SHADER_1_1) &&
		!driver->queryFeature(video::EVDF_ARB_VERTEX_PROGRAM_1))
	{
		device->getLogger()->log("WARNING: Vertex shaders disabled "\
			"because of missing driver/hardware support.");
		vsFileName = "";
	}


	s32 mirrorMatType = 0;

	video::IGPUProgrammingServices* gpu = driver->getGPUProgrammingServices();
	if (gpu)
	{
		MirrorShaderCallBack* mc = new MirrorShaderCallBack();

        mirrorMatType = gpu->addHighLevelShaderMaterialFromFiles(
				vsFileName, "vertexMain", video::EVST_VS_1_1,
				psFileName, "pixelMain", video::EPST_PS_1_1,
				mc, video::EMT_SOLID);

		mc->drop();
	}

    // MIRROR SIZE
    dimension2df mSize (1024.f,1024.f); 

    // RTT
    ITexture* mirrorTexRTT = driver->addRenderTargetTexture 
        (dimension2du(mSize), "mirrorTexRTT", ECF_A8R8G8B8);


	//irrMirrorNode* mirrorNode = new irrMirrorNode (mSize, smgr->getRootSceneNode(), smgr);

    // MIRROR MESH
    SMesh* mesh = new SMesh ();
    SMeshBuffer* buf = new SMeshBuffer ();
    mesh->addMeshBuffer (buf);

    const SColor white(255, 255, 255, 255);

    buf->Vertices.set_used (4);
    buf->Vertices[0].Pos = vector3df (0, 0, 0);                                           //   0-------1   
    buf->Vertices[0].TCoords = vector2df(0, 1);                                           //   | \     |   
    buf->Vertices[0].Color = white;                                                       //   |   \   |  
    buf->Vertices[1].Pos = vector3df (mSize.Width, 0, 0);                                 //   |     \ | 
    buf->Vertices[1].TCoords = vector2df(1, 1);                                           //   3-------2 
    buf->Vertices[1].Color = white;
    buf->Vertices[2].Pos = vector3df (mSize.Width, 0, -mSize.Height);
    buf->Vertices[2].TCoords = vector2df(1, 0);
    buf->Vertices[2].Color = white;
    buf->Vertices[3].Pos = vector3df (0, 0, -mSize.Height);
    buf->Vertices[3].TCoords = vector2df(0, 0);
    buf->Vertices[3].Color = white;

    for (u16 i=0; i<4; i++)
        buf->Vertices[i].Normal = vector3df (0,1.0,0);

    buf->Indices.set_used (6);
    buf->Indices[0] = 0;    
    buf->Indices[1] = 1;
    buf->Indices[2] = 2;
    buf->Indices[3] = 0;
    buf->Indices[4] = 2;
    buf->Indices[5] = 3;

    buf->recalculateBoundingBox();
    mesh->recalculateBoundingBox();


    // MIRROR NODE
    IMeshSceneNode* mirrorNode = smgr->addMeshSceneNode (mesh);

    mirrorNode->setMaterialFlag (EMF_LIGHTING, false);
    mirrorNode->setMaterialTexture (0, mirrorTexRTT);
    mirrorNode->setMaterialTexture (1, driver->getTexture("./grayCheckers.png"));

    mirrorNode->setMaterialType ((video::E_MATERIAL_TYPE) mirrorMatType);
    mirrorNode->setPosition (vector3df (-512,0,512));


	// add a nice skybox
    /*
	driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false);

	smgr->addSkyBoxSceneNode(
		driver->getTexture ("./media/skyboxUP.jpg"),
		driver->getTexture ("./media/skyboxDOWN.jpg"),
		driver->getTexture ("./media/skyboxLEFT.jpg"),
		driver->getTexture ("./media/skyboxRIGHT.jpg"),
		driver->getTexture ("./media/skyboxFRONT.jpg"),
		driver->getTexture ("./media/skyboxBACK.jpg"));

	driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, true);
    */

    // Env box
    IMeshSceneNode* envBox = smgr->addCubeSceneNode (1024);
    smgr->getMeshManipulator()->flipSurfaces (envBox->getMesh());
    envBox->setName ("envBox");
	envBox->setMaterialFlag (video::EMF_LIGHTING, false);
	envBox->setMaterialTexture (0, driver->getTexture ("./testCheckers.png"));


    // CAMERA
	scene::ICameraSceneNode* cam = smgr->addCameraSceneNodeFPS();
	cam->setPosition (core::vector3df (500,300,-400));
	cam->setTarget (core::vector3df (300,100,-200));

    scene::ICameraSceneNode* mCam = smgr->addCameraSceneNode();
    mCam->setUpVector (vector3df (0,-1,0));


    // CUBE
    IMeshSceneNode* cube = smgr->addCubeSceneNode (50);
	cube->setPosition (core::vector3df(300,100,-200));
	cube->setMaterialTexture (0, driver->getTexture ("./testCheckers.png"));
	cube->setMaterialFlag (video::EMF_LIGHTING, false);

	ISceneNodeAnimator* anim = smgr->createRotationAnimator(core::vector3df(0.3f,0.3f,0));
	cube->addAnimator(anim);
	anim->drop();
    


	device->getCursorControl()->setVisible(false);

	int lastFPS = -1;

	while(device->run())
		if (device->isWindowActive())
	{
		driver->beginScene(true, true, video::SColor(255,190,190,190));

            mCam->setPosition (cam->getAbsolutePosition() * vector3df (1,-1,1));
            mCam->setTarget (cam->getTarget() * vector3df (1,-1,1));
            smgr->setActiveCamera (mCam);
            mirrorNode->setVisible (false);
            
            driver->setRenderTarget (mirrorTexRTT, true, true, SColor(0,0,0,0));
                smgr->drawAll();

            mirrorNode->setVisible (true);
            smgr->setActiveCamera (cam);
            driver->setRenderTarget (0, true, true, SColor(0,0,0,0));
                smgr->drawAll();

		driver->endScene();

		int fps = driver->getFPS();

		if (lastFPS != fps)
		{
			core::stringw str = L"Irrlicht Engine - Mirror Node [";
			str += driver->getName();
			str += "] FPS:";
			str += fps;

			device->setWindowCaption (str.c_str());
			lastFPS = fps;
		}
	}

	device->drop();

	return 0;
}


